﻿using Fabrication.Tests.TestClasses;
using NUnit.Framework;
using System;

namespace Fabrication.Tests
{
    [TestFixture]
    public class hierarchy_rules
    {
        [Test]
        public void should_set_up_hierarchy()
        {
            var builtItem = new FabricationScope().Setup<TestClasses.Parent>()
                .ForChild(x => x.Child).Link(c => c.ParentId).To(x => x.Id)
                .Generate();

            Assert.That(builtItem.Child.ParentId, Is.Not.Null.And.EqualTo(builtItem.Id));
        }

        [Test]
        public void should_build_item_with_direct_recursive_hierarchy()
        {
            var builtItem = new FabricationScope().Generate<DirectRecursive>();

            Assert.That(builtItem, Is.Not.Null);
            Assert.That(builtItem.InnerDirect, Is.Not.Null);
            Assert.That(builtItem.InnerDirect.InnerDirect, Is.Not.Null);
            Assert.That(builtItem.InnerDirect.InnerDirect.InnerDirect, Is.Null);
        }

        [Test]
        public void should_build_item_with_indirect_recursive_hierarchy()
        {
            var builtItem = new FabricationScope().Generate<IndirectRecursive>();

            Assert.That(builtItem, Is.Not.Null);
            Assert.That(builtItem.InnerIndirect, Is.Not.Null);
            Assert.That(builtItem.InnerIndirect.InnerDirect, Is.Not.Null);
            Assert.That(builtItem.InnerIndirect.InnerDirect.InnerIndirect, Is.Not.Null);
            Assert.That(builtItem.InnerIndirect.InnerDirect.InnerIndirect.InnerDirect, Is.Not.Null);
            Assert.That(builtItem.InnerIndirect.InnerDirect.InnerIndirect.InnerDirect.InnerIndirect, Is.Null);
        }

        [Test]
        public void should_handle_linking_child_to_generated_property_in_parent()
        {
            var builtItem = new FabricationScope().Setup<TestClasses.Parent>()
                .For(x => x.Name).GenerateFullName()
                .ForChild(x => x.Child).Link(c => c.ParentName).To(x => x.Name)
                .Generate();

            Assert.That(builtItem.Child.ParentName, Is.Not.Null.And.EqualTo(builtItem.Name));
        }

        [Test]
        public void should_ignore_order_when_linking_child_to_generated_property_in_parent()
        {
            var builtItem = new FabricationScope().Setup<TestClasses.Parent>()
                .ForChild(x => x.Child).Link(c => c.ParentName).To(x => x.Name)
                .For(x => x.Name).GenerateFullName()
                .Generate();

            Assert.That(builtItem.Child.ParentName, Is.Not.Null.And.EqualTo(builtItem.Name));
        }

        [Test]
        public void should_ignore_type_rule_when_generated_within_a_hierarchy()
        {
            var scope = new FabricationScope();

            scope.Setup<LinkedChild>()
                .For(x => x.ParentId).Use(Guid.NewGuid());

            scope.Setup<TestClasses.Parent>()
                .ForChild(x => x.Child).Link(c => c.ParentId).To(p => p.Id);

            var builtItem = scope.Generate<TestClasses.Parent>();

            Assert.That(builtItem.Child.ParentId, Is.Not.Null.And.EqualTo(builtItem.Id));
        }

        [Test]
        public void should_ignore_linked_value_when_not_generated_within_a_hierarchy()
        {
            var id = Guid.NewGuid();

            var scope = new FabricationScope();

            scope.Setup<LinkedChild>()
                .For(x => x.ParentId).Use(id);

            scope.Setup<TestClasses.Parent>()
                .ForChild(x => x.Child).Link(c => c.ParentId).To(p => p.Id);

            var builtItem = scope.Generate<LinkedChild>();

            Assert.That(builtItem.ParentId, Is.Not.Null.And.EqualTo(id));
        }

        [Test]
        public void should_handle_internal_and_hierarchical_links_with_same_target()
        {
            var builtItem = new FabricationScope().Setup<StringsAndChild>()
                .For(x => x.FirstValue).GenerateFullName()
                .Link(x => x.SecondValue).To(x => x.FirstValue)
                .ForChild(x => x.Child).Link(x => x.ParentName).To(x => x.FirstValue)
                .Generate();

            Assert.That(builtItem.FirstValue, Is.Not.Null.And.Not.ContainsSubstring("StringGeneratedFullName"));
            Assert.That(builtItem.SecondValue, Is.EqualTo(builtItem.FirstValue));
            Assert.That(builtItem.Child.ParentName, Is.EqualTo(builtItem.FirstValue));
        }

        [Test]
        public void should_only_apply_child_rule_to_specific_property_if_linked_child_property_occurs_first() 
        {
            var builtItem = new FabricationScope().Setup<MultipleChildPropertiesWithLinkedChildDeclaredFirst>()
                .ForChild(x => x.Child).Link(x => x.ParentName).To(x => x.StringGeneratedFullName)
                .Generate();

            Assert.That(builtItem.Child.ParentName, Is.EqualTo(builtItem.StringGeneratedFullName));
        }

        [Test]
        public void should_only_apply_child_rule_to_specific_property_if_linked_child_property_occurs_last()
        {
            var builtItem = new FabricationScope().Setup<MultipleChildPropertiesWithLinkedChildDeclaredLast>()
                .ForChild(x => x.Child).Link(x => x.ParentName).To(x => x.StringGeneratedFullName)
                .Generate();

            Assert.That(builtItem.Child.ParentName, Is.EqualTo(builtItem.StringGeneratedFullName));
        }

        [Test]
        public void should_handle_child_rule_if_child_property_declared_before_link_target()
        {
            var builtItem = new FabricationScope().Setup<ChildDeclaredBeforeTarget>()
                .For(x => x.StringGeneratedFullName).GenerateFullName()
                .ForChild(x => x.Child).Link(x => x.ParentName).To(x => x.StringGeneratedFullName)
                .Generate();

            Assert.That(builtItem.StringGeneratedFullName, Is.Not.Null.And.Not.ContainsSubstring("StringGeneratedFullName"));
            Assert.That(builtItem.Child.ParentName, Is.EqualTo(builtItem.StringGeneratedFullName));
        }

        [Test]
        public void should_handle_child_rule_if_child_property_declared_after_link_target()
        {
            var builtItem = new FabricationScope().Setup<ChildDeclaredAfterTarget>()
                .For(x => x.StringGeneratedFullName).GenerateFullName()
                .ForChild(x => x.Child).Link(x => x.ParentName).To(x => x.StringGeneratedFullName)
                .Generate();

            Assert.That(builtItem.StringGeneratedFullName, Is.Not.Null.And.Not.ContainsSubstring("StringGeneratedFullName"));
            Assert.That(builtItem.Child.ParentName, Is.EqualTo(builtItem.StringGeneratedFullName));
        }

        [Test]
        public void should_populate_properties_of_child_classes()
        {
            var builtItem = Fabricator.Generate<Parent>();
            Assert.IsNotNull(builtItem.Reference.DecProp);
        }

        [Test]
        public void should_handle_child_properties_with_same_name_as_parent_property()
        {
            var builtItem = new FabricationScope().Setup<ParentWithPropertyOfSameName>()
                .Generate();

            Assert.That(builtItem, Is.Not.Null);
            Assert.That(builtItem.SamePropertyName, Is.Not.Null);
            Assert.That(builtItem.Child, Is.Not.Null);
            Assert.That(builtItem.Child.SamePropertyName, Is.Not.Null);
        }
    }

    public class ClassWithDecimalProp
    {
        public decimal DecProp { get; set; }
    }
    public class Parent
    {
        public int Id { get; set; }
        public ClassWithDecimalProp Reference { get; set; }
    }

    public class StringsAndChild
    {
        public string FirstValue { get; set; }
        public string SecondValue { get; set; }
        public InnerClass Child { get; set; }
    }

    public class MultipleChildPropertiesWithLinkedChildDeclaredLast
    {
        public InnerClass InnerClassDefault { get; set; }
        public InnerClass Child { get; set; }

        public string StringGeneratedFullName { get; set; }
    }

    public class MultipleChildPropertiesWithLinkedChildDeclaredFirst
    {
        public InnerClass Child { get; set; }
        public InnerClass InnerClassDefault { get; set; }

        public string StringGeneratedFullName { get; set; }
    }

    public class ChildDeclaredBeforeTarget
    {
        public InnerClass Child { get; set; }
        public string StringGeneratedFullName { get; set; }
    }

    public class ChildDeclaredAfterTarget
    {
        public string StringGeneratedFullName { get; set; }
        public InnerClass Child { get; set; }
    }
}
